/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import {ArkFile, ts} from 'arkanalyzer';
import {BaseChecker, BaseMetaData} from '../BaseChecker';
import {Defects} from '../../Index';
import {AstMatcher, MatcherCallback, MatcherTypes} from '../../Index';
import {Rule} from '../../Index';
import {RuleListUtil} from '../../utils/common/DefectsList';
import {IssueReport} from '../../model/Defects';

const gMetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: 'docs/no-non-null-asserted-optional-chain.md',
    description: 'Disallow non-null assertions after an optional chain expression.'
};

export class NoNonNullAssertedOptionalChainCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private buildMatcher: AstMatcher = {
        matcherType: MatcherTypes.AST,
        match: (node: ts.Node): boolean => {
            return ts.isNonNullExpression(node) && 
            !(ts.isCallExpression(node.parent) && node.parent.expression === node); //obj?.foo!();避免这种情况报警
        }
    };

    public registerMatchers(): MatcherCallback[] {
        const matchBuildCb: MatcherCallback = {
            matcher: this.buildMatcher,
            callback: this.check
        };
        return [matchBuildCb];
    };

    public check = (node: ts.NonNullExpression, targetFile: ArkFile) => {
        const optionalChainNode = this.findOptionalChainNode(node.expression);
        if (!optionalChainNode) {
            return;
        }

        const sourceFile = optionalChainNode.getSourceFile();
        const startPos = sourceFile.getLineAndCharacterOfPosition(optionalChainNode.getStart());
        const endPos = sourceFile.getLineAndCharacterOfPosition(optionalChainNode.getEnd());
        this.addIssueReport(targetFile, startPos.line + 1, startPos.character + 1, endPos.character);
    };
    
    private findOptionalChainNode(expr: ts.Expression): ts.Node | undefined {
        if (!expr) {
            return undefined;
        }
        // isElementAccessExpression: arr?.[0]!   isCallExpression: @MyDecorator?.()! 这两种情况很少
        if ((ts.isPropertyAccessExpression(expr) || ts.isElementAccessExpression(expr) || ts.isCallExpression(expr)) && expr.questionDotToken) {
            return expr;
        }

        // 递归检查更深层的表达式
        if (ts.isCallExpression(expr) || //instance?.foo()
            ts.isParenthesizedExpression(expr) //(instance?.getNum())
        ) {
            return this.findOptionalChainNode(expr.expression);
        }

        return undefined;
    }

    private addIssueReport(arkFile: ArkFile, line: number, startCol: number, endCol: number): void {
        const severity = this.rule.alert ?? this.metaData.severity;
        const filePath = arkFile.getFilePath();
        const defect = new Defects(line, startCol, endCol, this.metaData.description, severity, this.rule.ruleId,
        filePath, this.metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }
}